package ga;

import ga.chromosome.Chromosome;
import ga.chromosome.Population;
import ga.gene.TheException;

/**
 * The Class Algorithm.
 */
public class Algorithm {
	
	/* GA parameters */
	private static final double uniformRate = 0.05;
    private static final double mutationRate = 0.5;
    private static final int tournamentSize = 5;
    private static final boolean elitism = true;
	
    /**
     * Evolves population. Takes a population and returns a new population that has 
     * gone through crossover and mutation using the above variables.
     *
     * @param pop the pop
     * @return the population
     * @throws TheException the the exception
     */
    public static Population evolvePopulation(Population pop) throws TheException {
        Population newPopulation = new Population(pop.size());

        // Keep our best individual
        if (elitism) {
            newPopulation.setChromosome(0, pop.getFittest());
        }

        // Crossover population
        int elitismOffset;
        if (elitism) {
            elitismOffset = 1;
        } else {
            elitismOffset = 0;
        }
        // Loop over the population size and create new individuals with
        // crossover
        for (int i = elitismOffset; i < pop.size(); i++) {
            Chromosome chromo1 = tourney(pop);
            Chromosome chromo2 = tourney(pop);
            Chromosome newChromo = crossover(chromo1, chromo2);
            newPopulation.setChromosome(i, newChromo);
        }

//        // Mutate population
//        for (int i = elitismOffset; i < newPopulation.size(); i++) {
//            mutate(newPopulation.getChromosome(i));
//        }

        return newPopulation;
    }
    
	/**
	 * Takes two chromosomes and returns one that is a combination of the two.
	 *
	 * @param chromo1 the chromo1
	 * @param chromo2 the chromo2
	 * @return the new chromosome
	 * @throws TheException the the exception
	 */
	private static Chromosome crossover(Chromosome chromo1, Chromosome chromo2) throws TheException {
		Chromosome newSol = new Chromosome(chromo1);
		
		// Loop through genes
        for (int i = 1; i <= Chromosome.getDefaultGenes(); i++) {
            // Crossover
            if (Math.random() > uniformRate) 
                newSol.setGene(i, chromo2.getGene(i));
            
        }
        for(int i  = 1; i <= Chromosome.getDefaultInputs(); i++){
        	if (Math.random() > uniformRate) 
                newSol.setInput(i, chromo2.getInput(i));
          
        }
        newSol.solve();
        return newSol;
    }
	
	/**
	 * Simply will mutate a single gene of a chromosome.
	 *
	 * @param chromo the chromo
	 * @throws TheException the the exception
	 */
	private static void mutate(Chromosome chromo) throws TheException{
		for (int i = 1; i <= Chromosome.getDefaultGenes(); i++){
			if(Math.random() <= mutationRate)
				chromo.mutate(i);
		}
	}
	
	/**
	 * Tournament selection.  Takes in a population and depending on the tournament
	 * size will grab that many chromosomes and only return the fittest.
	 *
	 * @param pop the pop
	 * @return the chromosome
	 * @throws TheException the the exception
	 */
	private static Chromosome tourney(Population pop) throws TheException{
		Population tournament = new Population(tournamentSize);
		for (int i = 0; i < tournamentSize; i++) {
            int randomId = (int) (Math.random() * pop.size());
            tournament.setChromosome(i, pop.getChromosome(randomId));
        }
		return tournament.getFittest();
		
	}
}
